// Copyright (c) 2025 Inochi Amaoto

/* SPDX-License-Identifier: GPL-2.0 */

#define pr_fmt(fmt) "virtio-vapp: " fmt

#include <linux/module.h>

#include "virtio-vapp.h"
#include "virtio-vapp-dev.h"

static unsigned int feature_table[] = { };

static struct virtio_vapp_dev __rcu* virtio_vapp_ptr;
static DEFINE_MUTEX(virtio_vapp_mutex);

struct virtio_vapp_dev* virtio_vapp_get_dev_rcu(void)
{
  return rcu_dereference(virtio_vapp_ptr);
}

static int virtio_vapp_get_config(struct virtio_device* vdev, struct virtio_vapp_cpu_config* conf)
{
  struct virtio_vapp_config config;
  pr_info("ready to get config\n");
  virtio_cread_le(vdev, struct virtio_vapp_config, uuid, &config.uuid);
  virtio_cread_le(vdev, struct virtio_vapp_config, multiqueues, &config.multiqueues);

  conf->uuid = le64_to_cpu(config.uuid);
  conf->multiqueues = le64_to_cpu(config.multiqueues);

  pr_info("get config: uuid %llu\n", conf->uuid);
  pr_info("get config: multiqueues %llu\n", conf->multiqueues);

  return 0;
}

int probe_virtio_vapp(struct virtio_device* vdev)
{
  int ret;
  struct virtio_vapp_cpu_config config;
  struct virtio_vapp_dev* vapp;
  size_t i;

  pr_info("probe called\n");

  ret = mutex_lock_interruptible(&virtio_vapp_mutex);
  if (ret)
    return ret;

  if (rcu_dereference_protected(virtio_vapp_ptr, lockdep_is_held(&virtio_vapp_mutex))) {
    ret = -EBUSY;
    goto failed;
  }

  if (!vdev->config->get) {
    dev_err(&vdev->dev, "%s failure: config access disabled\n", __func__);
    ret = -EINVAL;
    goto failed;
  }

  ret = virtio_vapp_get_config(vdev, &config);
  if (ret) {
    pr_info("get config failed\n");
    goto failed;
  }

  vapp = virtio_vapp_dev_alloc(vdev, &config);
  if (!vapp) {
    ret = -ENOMEM;
    goto failed;
  }
  pr_info("virtio vapp alloced\n");

  ret = vio_vapp_init();
  if (ret) {
    goto vio_failed;
  }
  pr_info("vio vapp inited\n");

  ret = virtio_vapp_dev_assign_virtqueue(vapp);
  if (ret) {
    goto vq_failed;
  }
  pr_info("virtqueue assigned\n");

  // FIXME: fill queue, others
  virtio_vapp_dtq_lock_rx(&vapp->cdtq);
  virtio_vapp_dtq_ctrl_rx_fill(&vapp->cdtq);
  virtio_vapp_dtq_unlock_rx(&vapp->cdtq);
  for (i = 0; i < vapp->config.multiqueues; ++i) {
    virtio_vapp_dtq_lock_rx(&vapp->ddtqs[i]);
    virtio_vapp_dtq_data_rx_fill(&vapp->ddtqs[i]);
    virtio_vapp_dtq_unlock_rx(&vapp->ddtqs[i]);
  }
  pr_info("virtqueue filled\n");

  virtio_vapp_dtq_start(&vapp->cdtq);
  for (i = 0; i < vapp->config.multiqueues; ++i)
    virtio_vapp_dtq_start(&vapp->ddtqs[i]);
  pr_info("virtqueue started\n");

  vdev->priv = vapp;
  rcu_assign_pointer(virtio_vapp_ptr, vapp);
  pr_info("vapp dev %p\n", virtio_vapp_get_dev_rcu);

  mutex_unlock(&virtio_vapp_mutex);

  return 0;

 vq_failed:
  vio_vapp_exit();
 vio_failed:
  virtio_vapp_dev_free(vapp);
 failed:
  mutex_unlock(&virtio_vapp_mutex);
  return ret;
}

void remove_virtio_vapp(struct virtio_device *vdev)
{
  struct virtio_vapp_dev* vapp = vdev->priv;
  size_t i;

  mutex_lock(&virtio_vapp_mutex);

  vdev->priv = NULL;
  rcu_assign_pointer(virtio_vapp_ptr, NULL);
  synchronize_rcu();

  virtio_vapp_dtq_stop(&vapp->cdtq);
  for (i = 0; i < vapp->config.multiqueues; ++i)
    virtio_vapp_dtq_stop(&vapp->ddtqs[i]);

  vdev->config->reset(vdev);

  virtio_vapp_ctrl_dtq_clean_virtqueue(&vapp->cdtq);
  for (i = 0; i < vapp->config.multiqueues; ++i) {
    virtio_vapp_data_dtq_clean_virtqueue(&vapp->ddtqs[i]);
  }
  vapp_send_queue_destory(&vapp->cqueue, struct vapp_send_ctrl_data_packet, node, vapp_send_ctrl_data_packet_free);
  vapp_send_queue_destory(&vapp->dqueue, struct vapp_send_data_packet, node, vapp_send_data_packet_free);

  // TODO: sender

  vdev->config->del_vqs(vdev);

  virtio_vapp_dtq_flush_work(&vapp->cdtq);
  for (i = 0; i < vapp->config.multiqueues; ++i)
    virtio_vapp_dtq_flush_work(&vapp->ddtqs[i]);

  mutex_unlock(&virtio_vapp_mutex);

  virtio_vapp_dev_free(vapp);
}

static struct virtio_device_id id_table[] = {
  { VIRTIO_ID_VAPP, VIRTIO_DEV_ANY_ID },
  { 0 },
};

static struct virtio_driver virtio_vapp_driver = {
  .driver.name = KBUILD_MODNAME,
  .driver.owner = THIS_MODULE,
  .id_table = id_table,
  .feature_table = feature_table,
  .feature_table_size = ARRAY_SIZE(feature_table),
  .probe = probe_virtio_vapp,
  .remove = remove_virtio_vapp,
};

static int __init virtio_vapp_init(void)
{
  int ret = vio_dtq_init();
  if (ret < 0)
    return -ENOMEM;

  ret = register_virtio_driver(&virtio_vapp_driver);
  if (ret)
    goto failed;

  return 0;

 failed:
  vio_dtq_exit();
  return ret;
}

static void __exit virtio_vapp_exit(void)
{
  unregister_virtio_driver(&virtio_vapp_driver);
  vio_dtq_exit();
}

module_init(virtio_vapp_init);
module_exit(virtio_vapp_exit);
MODULE_AUTHOR("Inochi Amaoto");
MODULE_DESCRIPTION("virtio virtual application front-end driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("devname:virtio-vapp");
